/***
 *
 *  rtcfg/rtcfg_conn_event.c
 *
 *  Real-Time Configuration Distribution Protocol
 *
 *  Copyright (C) 2003-2005 Jan Kiszka <jan.kiszka@web.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/kernel.h>

#include <ipv4/route.h>
#include <rtcfg/rtcfg.h>
#include <rtcfg/rtcfg_conn_event.h>
#include <rtcfg/rtcfg_event.h>
#include <rtcfg/rtcfg_frame.h>

/****************************** states ***************************************/
static int rtcfg_conn_state_searching(struct rtcfg_connection *conn,
				      RTCFG_EVENT event_id, void *event_data);
static int rtcfg_conn_state_stage_1(struct rtcfg_connection *conn,
				    RTCFG_EVENT event_id, void *event_data);
static int rtcfg_conn_state_stage_2(struct rtcfg_connection *conn,
				    RTCFG_EVENT event_id, void *event_data);
static int rtcfg_conn_state_ready(struct rtcfg_connection *conn,
				  RTCFG_EVENT event_id, void *event_data);
static int rtcfg_conn_state_dead(struct rtcfg_connection *conn,
				 RTCFG_EVENT event_id, void *event_data);

#ifdef CONFIG_XENO_DRIVERS_NET_RTCFG_DEBUG
const char *rtcfg_conn_state[] = { "RTCFG_CONN_SEARCHING", "RTCFG_CONN_STAGE_1",
				   "RTCFG_CONN_STAGE_2", "RTCFG_CONN_READY",
				   "RTCFG_CONN_DEAD" };
#endif /* CONFIG_XENO_DRIVERS_NET_RTCFG_DEBUG */

static void rtcfg_conn_recv_announce_new(struct rtcfg_connection *conn,
					 struct rtskb *rtskb);
static void rtcfg_conn_check_cfg_timeout(struct rtcfg_connection *conn);
static void rtcfg_conn_check_heartbeat(struct rtcfg_connection *conn);

static int (*state[])(struct rtcfg_connection *conn, RTCFG_EVENT event_id,
		      void *event_data) = {
	rtcfg_conn_state_searching, rtcfg_conn_state_stage_1,
	rtcfg_conn_state_stage_2, rtcfg_conn_state_ready, rtcfg_conn_state_dead
};

int rtcfg_do_conn_event(struct rtcfg_connection *conn, RTCFG_EVENT event_id,
			void *event_data)
{
	int conn_state = conn->state;

	RTCFG_DEBUG(3, "RTcfg: %s() conn=%p, event=%s, state=%s\n",
		    __FUNCTION__, conn, rtcfg_event[event_id],
		    rtcfg_conn_state[conn_state]);

	return (*state[conn_state])(conn, event_id, event_data);
}

static void rtcfg_next_conn_state(struct rtcfg_connection *conn,
				  RTCFG_CONN_STATE state)
{
	RTCFG_DEBUG(4, "RTcfg: next connection state=%s \n",
		    rtcfg_conn_state[state]);

	conn->state = state;
}

static int rtcfg_conn_state_searching(struct rtcfg_connection *conn,
				      RTCFG_EVENT event_id, void *event_data)
{
	struct rtcfg_device *rtcfg_dev = &device[conn->ifindex];
	struct rtskb *rtskb = (struct rtskb *)event_data;

	switch (event_id) {
	case RTCFG_FRM_ANNOUNCE_NEW:
		rtcfg_conn_recv_announce_new(conn, rtskb);
		break;

	case RTCFG_FRM_ANNOUNCE_REPLY:
		conn->last_frame = rtskb->time_stamp;

		rtcfg_next_conn_state(conn, RTCFG_CONN_READY);

		rtcfg_dev->stations_found++;
		rtcfg_dev->stations_ready++;
		rtcfg_dev->spec.srv.clients_configured++;
		if (rtcfg_dev->spec.srv.clients_configured ==
		    rtcfg_dev->other_stations)
			rtcfg_complete_cmd(conn->ifindex, RTCFG_CMD_WAIT, 0);

		break;

	default:
		RTCFG_DEBUG(1, "RTcfg: unknown event %s for conn %p in %s()\n",
			    rtcfg_event[event_id], conn, __FUNCTION__);
		return -EINVAL;
	}
	return 0;
}

static int rtcfg_conn_state_stage_1(struct rtcfg_connection *conn,
				    RTCFG_EVENT event_id, void *event_data)
{
	struct rtskb *rtskb = (struct rtskb *)event_data;
	struct rtcfg_device *rtcfg_dev = &device[conn->ifindex];
	struct rtcfg_frm_ack_cfg *ack_cfg;
	int packets;

	switch (event_id) {
	case RTCFG_FRM_ACK_CFG:
		conn->last_frame = rtskb->time_stamp;

		ack_cfg = (struct rtcfg_frm_ack_cfg *)rtskb->data;
		conn->cfg_offs = ntohl(ack_cfg->ack_len);

		if ((conn->flags & _RTCFG_FLAG_STAGE_2_DATA) != 0) {
			if (conn->cfg_offs >= conn->stage2_file->size) {
				rtcfg_dev->spec.srv.clients_configured++;
				if (rtcfg_dev->spec.srv.clients_configured ==
				    rtcfg_dev->other_stations)
					rtcfg_complete_cmd(conn->ifindex,
							   RTCFG_CMD_WAIT, 0);
				rtcfg_next_conn_state(
					conn, ((conn->flags &
						_RTCFG_FLAG_READY) != 0) ?
						      RTCFG_CONN_READY :
						      RTCFG_CONN_STAGE_2);
			} else {
				packets = conn->burstrate;
				while ((conn->cfg_offs <
					conn->stage2_file->size) &&
				       (packets > 0)) {
					rtcfg_send_stage_2_frag(conn);
					packets--;
				}
			}
		} else {
			rtcfg_dev->spec.srv.clients_configured++;
			if (rtcfg_dev->spec.srv.clients_configured ==
			    rtcfg_dev->other_stations)
				rtcfg_complete_cmd(conn->ifindex,
						   RTCFG_CMD_WAIT, 0);
			rtcfg_next_conn_state(
				conn, ((conn->flags & _RTCFG_FLAG_READY) != 0) ?
					      RTCFG_CONN_READY :
					      RTCFG_CONN_STAGE_2);
		}

		break;

	case RTCFG_TIMER:
		rtcfg_conn_check_cfg_timeout(conn);
		break;

	default:
		RTCFG_DEBUG(1, "RTcfg: unknown event %s for conn %p in %s()\n",
			    rtcfg_event[event_id], conn, __FUNCTION__);
		return -EINVAL;
	}
	return 0;
}

static int rtcfg_conn_state_stage_2(struct rtcfg_connection *conn,
				    RTCFG_EVENT event_id, void *event_data)
{
	struct rtskb *rtskb = (struct rtskb *)event_data;
	struct rtcfg_device *rtcfg_dev = &device[conn->ifindex];

	switch (event_id) {
	case RTCFG_FRM_READY:
		conn->last_frame = rtskb->time_stamp;

		rtcfg_next_conn_state(conn, RTCFG_CONN_READY);

		conn->flags |= _RTCFG_FLAG_READY;
		rtcfg_dev->stations_ready++;

		if (rtcfg_dev->stations_ready == rtcfg_dev->other_stations)
			rtcfg_complete_cmd(conn->ifindex, RTCFG_CMD_READY, 0);

		break;

	case RTCFG_TIMER:
		rtcfg_conn_check_cfg_timeout(conn);
		break;

	default:
		RTCFG_DEBUG(1, "RTcfg: unknown event %s for conn %p in %s()\n",
			    rtcfg_event[event_id], conn, __FUNCTION__);
		return -EINVAL;
	}
	return 0;
}

static int rtcfg_conn_state_ready(struct rtcfg_connection *conn,
				  RTCFG_EVENT event_id, void *event_data)
{
	struct rtskb *rtskb = (struct rtskb *)event_data;

	switch (event_id) {
	case RTCFG_TIMER:
		rtcfg_conn_check_heartbeat(conn);
		break;

	case RTCFG_FRM_HEARTBEAT:
		conn->last_frame = rtskb->time_stamp;
		break;

	default:
		RTCFG_DEBUG(1, "RTcfg: unknown event %s for conn %p in %s()\n",
			    rtcfg_event[event_id], conn, __FUNCTION__);
		return -EINVAL;
	}
	return 0;
}

static int rtcfg_conn_state_dead(struct rtcfg_connection *conn,
				 RTCFG_EVENT event_id, void *event_data)
{
	switch (event_id) {
	case RTCFG_FRM_ANNOUNCE_NEW:
		rtcfg_conn_recv_announce_new(conn, (struct rtskb *)event_data);
		break;

	case RTCFG_FRM_ANNOUNCE_REPLY:
		/* Spec to-do: signal station that it is assumed to be dead
               (=> reboot command?) */

	default:
		RTCFG_DEBUG(1, "RTcfg: unknown event %s for conn %p in %s()\n",
			    rtcfg_event[event_id], conn, __FUNCTION__);
		return -EINVAL;
	}
	return 0;
}

static void rtcfg_conn_recv_announce_new(struct rtcfg_connection *conn,
					 struct rtskb *rtskb)
{
	struct rtcfg_device *rtcfg_dev = &device[conn->ifindex];
	struct rtcfg_frm_announce *announce_new;
	int packets;

	conn->last_frame = rtskb->time_stamp;

	announce_new = (struct rtcfg_frm_announce *)rtskb->data;

	conn->flags = announce_new->flags;
	if (announce_new->burstrate < conn->burstrate)
		conn->burstrate = announce_new->burstrate;

	rtcfg_next_conn_state(conn, RTCFG_CONN_STAGE_1);

	rtcfg_dev->stations_found++;
	if ((conn->flags & _RTCFG_FLAG_READY) != 0)
		rtcfg_dev->stations_ready++;

	if (((conn->flags & _RTCFG_FLAG_STAGE_2_DATA) != 0) &&
	    (conn->stage2_file != NULL)) {
		packets = conn->burstrate - 1;

		rtcfg_send_stage_2(conn, 1);

		while ((conn->cfg_offs < conn->stage2_file->size) &&
		       (packets > 0)) {
			rtcfg_send_stage_2_frag(conn);
			packets--;
		}
	} else {
		rtcfg_send_stage_2(conn, 0);
		conn->flags &= ~_RTCFG_FLAG_STAGE_2_DATA;
	}
}

static void rtcfg_conn_check_cfg_timeout(struct rtcfg_connection *conn)
{
	struct rtcfg_device *rtcfg_dev;

	if (!conn->cfg_timeout)
		return;

	if (rtdm_clock_read() >= conn->last_frame + conn->cfg_timeout) {
		rtcfg_dev = &device[conn->ifindex];

		rtcfg_dev->stations_found--;
		if (conn->state == RTCFG_CONN_STAGE_2)
			rtcfg_dev->spec.srv.clients_configured--;

		rtcfg_next_conn_state(conn, RTCFG_CONN_SEARCHING);
		conn->cfg_offs = 0;
		conn->flags = 0;

#if IS_ENABLED(CONFIG_XENO_DRIVERS_NET_RTIPV4)
		if (conn->addr_type == RTCFG_ADDR_IP) {
			struct rtnet_device *rtdev;

			/* MAC address yet unknown -> use broadcast address */
			rtdev = rtdev_get_by_index(conn->ifindex);
			if (rtdev == NULL)
				return;
			memcpy(conn->mac_addr, rtdev->broadcast, MAX_ADDR_LEN);
			rtdev_dereference(rtdev);
		}
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4 */
	}
}

static void rtcfg_conn_check_heartbeat(struct rtcfg_connection *conn)
{
	u64 timeout;
	struct rtcfg_device *rtcfg_dev;

	timeout = device[conn->ifindex].spec.srv.heartbeat_timeout;
	if (!timeout)
		return;

	if (rtdm_clock_read() >= conn->last_frame + timeout) {
		rtcfg_dev = &device[conn->ifindex];

		rtcfg_dev->stations_found--;
		rtcfg_dev->stations_ready--;
		rtcfg_dev->spec.srv.clients_configured--;

		rtcfg_send_dead_station(conn);

		rtcfg_next_conn_state(conn, RTCFG_CONN_DEAD);
		conn->cfg_offs = 0;
		conn->flags = 0;

#if IS_ENABLED(CONFIG_XENO_DRIVERS_NET_RTIPV4)
		if ((conn->addr_type & RTCFG_ADDR_MASK) == RTCFG_ADDR_IP) {
			struct rtnet_device *rtdev =
				rtdev_get_by_index(conn->ifindex);

			rt_ip_route_del_host(conn->addr.ip_addr, rtdev);

			if (rtdev == NULL)
				return;

			if (!(conn->addr_type & FLAG_ASSIGN_ADDR_BY_MAC))
				/* MAC address yet unknown -> use broadcast address */
				memcpy(conn->mac_addr, rtdev->broadcast,
				       MAX_ADDR_LEN);

			rtdev_dereference(rtdev);
		}
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4 */
	}
}
